/*******************************************************************************
 * Odbaul Mobile Client - XmlParser.java 
 * Copyright (c) Vernalis Systems pvt ltd  2009 
 * This file is licensed from, and is a trade secret of:
 * 	Vernalis Systems pvt ltd
 * 	1,Shakthi Nagar
 * 	Mount Poonamallee road Porur
 * 	Chennai -116
 * TERMS OF USE: Refer your Software License Agreement
 *  Elton Kent - initial API and implementation
 ******************************************************************************/
package com.aphrodite.parser;

import java.io.IOException;
import java.io.Reader;
import java.util.Vector;

/**
 * A simple, pull based "Common XML" parser. Attention: This class has been
 * renamed from DefaultParser for consitency with the org.kxml.io package.
 */

public class XmlParser extends AbstractXmlParser {

	/** creates a new Parser based on the give reader */

	class DefaultParserException extends ParseException {
		DefaultParserException(String msg, Exception chained) {
			super(msg, chained, XmlParser.this.line, XmlParser.this.column);
		}
	}

	private static final String UNEXPECTED_EOF = "Unexpected EOF";
	private char[] buf;
	private int bufCount;
	private int bufPos;
	int column = 1;
	private StartTag current;
	private boolean eof;
	private boolean immediateClose = false;

	int line = 1;

	/** The next event. May be null at startup. */

	private ParseEvent next;
	private Vector qNames = new Vector();

	private Reader reader;

	private boolean relaxed;

	public XmlParser(Reader reader) throws IOException {
		this(reader, Runtime.getRuntime().freeMemory() >= 1048576 ? 8192 : 1);
	}

	private XmlParser(Reader reader, int bufSize) throws IOException {

		this.reader = reader; // new LookAheadReader (reader);
		buf = new char[bufSize];
	}

	private ParseEvent parseCData() throws IOException {

		StringBuffer buf = readTo('[', new StringBuffer());

		if (!buf.toString().equals("CDATA"))
			throw new DefaultParserException("Invalid CDATA start!", null);

		buf.setLength(0);

		readChar(); // skip '['

		int c0 = readChar();
		int c1 = readChar();

		while (true) {
			int c2 = readChar();

			if (c2 == -1)
				throw new DefaultParserException(UNEXPECTED_EOF, null);

			if (c0 == ']' && c1 == ']' && c2 == '>')
				break;

			buf.append((char) c0);
			c0 = c1;
			c1 = c2;
		}

		return new ParseEvent(Xml.TEXT, buf.toString());
	}

	private ParseEvent parseComment() throws IOException {

		StringBuffer buf = new StringBuffer();

		if (readChar() != '-')
			throw new DefaultParserException("'-' expected", null);

		int cnt;
		int lst;

		while (true) {
			readTo('-', buf);

			if (readChar() == -1)
				throw new DefaultParserException(UNEXPECTED_EOF, null);

			cnt = 0;

			do {
				lst = readChar();
				cnt++; // adds one more, but first is not cnted
			} while (lst == '-');

			if (lst == '>' && cnt >= 2)
				break;

			while (cnt-- > 0)
				buf.append('-');

			buf.append((char) lst);
		}

		while (cnt-- > 2)
			buf.append('-');

		return new ParseEvent(Xml.COMMENT, buf.toString());
	}

	private ParseEvent parseDoctype() throws IOException {

		StringBuffer buf = new StringBuffer();
		int nesting = 1;

		while (true) {
			int i = readChar();
			switch (i) {
			case -1:
				throw new DefaultParserException(UNEXPECTED_EOF, null);
			case '<':
				nesting++;
				break;
			case '>':
				if ((--nesting) == 0)
					return new ParseEvent(Xml.DOCTYPE, buf.toString());
				break;
			}
			buf.append((char) i);
		}
	}

	private ParseEvent parseEndTag() throws IOException {

		skipWhitespace();
		String name = readName();
		skipWhitespace();

		if (readChar() != '>')
			throw new DefaultParserException("'>' expected", null);

		int last = qNames.size();
		while (true) {
			if (last == 0) {
				if (relaxed)
					return new ParseEvent(Xml.END_DOCUMENT, null);
				throw new DefaultParserException("tagstack empty parsing </" + name + ">", null);
			}
			String qName = (String) qNames.elementAt(--last);
			qNames.removeElementAt(last);

			if (qName.equals(name))
				break;
			if (!relaxed)
				throw new DefaultParserException("StartTag <" + qName + "> does not match end tag </" + name + ">",
						null);
			if (qName.toLowerCase().equals(name.toLowerCase()))
				break;
			current = current.parent;
		}

		Tag result = new Tag(Xml.END_TAG, current, current.namespace, current.name);

		current = current.parent;

		return result;
	}

	/** precondition: <? consumed */

	private ParseEvent parsePI() throws IOException {
		StringBuffer buf = new StringBuffer();
		readTo('?', buf);
		readChar(); // ?

		while (peekChar() != '>') {
			buf.append('?');

			int r = readChar();
			if (r == -1)
				throw new DefaultParserException(UNEXPECTED_EOF, null);

			buf.append((char) r);
			readTo('?', buf);
			readChar();
		}

		readChar(); // consume >

		return new ParseEvent(Xml.PROCESSING_INSTRUCTION, buf.toString());
	}

	/** precondition: &lt; consumed */

	private ParseEvent parseSpecial() throws IOException {

		switch (peekChar()) {
		case -1:
			throw new DefaultParserException(UNEXPECTED_EOF, null);

		case '!':
			readChar();
			switch (peekChar()) {
			case '-':
				readChar();
				return parseComment();

			case '[':
				readChar();
				return parseCData();

			default:
				return parseDoctype();
			}

		case '?':
			readChar();
			return parsePI();

		case '/':
			readChar();
			return parseEndTag();

		default:
			return parseStartTag();
		}
	}

	/* precondition: &lt;!- consumed */

	private StartTag parseStartTag() throws IOException {
		// current = new StartTag (current, reader, relaxed);

		// prefixMap = parent == null ? new PrefixMap () : parent.prefixMap;

		String qname = readName();

		// System.out.println ("name: ("+name+")");

		Vector attributes = null;
		immediateClose = false;

		while (true) {
			skipWhitespace();

			int c = peekChar();

			if (c == '/') {
				immediateClose = true;
				readChar();
				skipWhitespace();
				if (readChar() != '>')
					throw new DefaultParserException("illegal element termination", null);
				break;
			}

			if (c == '>') {
				readChar();
				break;
			}

			if (c == -1)
				throw new DefaultParserException(UNEXPECTED_EOF, null);

			String attrName = readName();

			if (attrName.length() == 0)
				throw new DefaultParserException("illegal char / attr", null);

			skipWhitespace();

			if (readChar() != '=')
				throw new DefaultParserException("Attribute name " + attrName + "must be followed by '='!", null);

			skipWhitespace();
			int delimiter = readChar();

			if (delimiter != '\'' && delimiter != '"') {
				if (!relaxed)
					throw new DefaultParserException("<" + qname + ">: invalid delimiter: " + (char) delimiter, null);

				delimiter = ' ';
			}

			StringBuffer buf = new StringBuffer();
			readText(buf, (char) delimiter);
			if (attributes == null)
				attributes = new Vector();
			attributes.addElement(new Attribute(null, attrName, buf.toString()));

			if (delimiter != ' ')
				readChar(); // skip endquote
		}

		try {
			current = new StartTag(current, Xml.NO_NAMESPACE, qname, attributes, immediateClose, processNamespaces);
		} catch (Exception e) {
			throw new DefaultParserException(e.toString(), e);
		}

		// System.out.println ("tag: ("+next+")");

		if (!immediateClose)
			qNames.addElement(qname);

		return current;
	}

	/* precondition: &lt! consumed */

	public ParseEvent peek() throws IOException {

		if (next == null) {

			if (immediateClose) {
				next = new Tag(Xml.END_TAG, current, current.namespace, current.name);
				current = current.getParent();
				immediateClose = false;
			} else {
				switch (peekChar()) {

				case '<':
					readChar();
					next = parseSpecial();
					break;

				case -1:
					if (current != null && !relaxed)
						throw new DefaultParserException("End tag missing for: " + current, null);
					next = new ParseEvent(Xml.END_DOCUMENT, null);
					break;

				default: {
					StringBuffer buf = new StringBuffer();
					int type = readText(buf, '<');
					next = new ParseEvent(type, buf.toString());
				}
				}
			}
		}
		return next;
	}

	private int peekChar() throws IOException {
		if (eof)
			return -1;

		if (bufPos >= bufCount) {

			if (buf.length == 1) {
				int c = reader.read();
				if (c == -1) {
					eof = true;
					return -1;
				}

				bufCount = 1;
				buf[0] = (char) c;
			} else {
				bufCount = reader.read(buf, 0, buf.length);

				if (bufCount == -1) {
					eof = true;
					return -1;
				}
			}

			bufPos = 0;
		}

		return buf[bufPos];
	}

	/* precondition: &lt;/ consumed */

	public ParseEvent read() throws IOException {
		if (next == null)
			peek();

		ParseEvent result = next;
		next = null;
		return result;
	}

	public int readChar() throws IOException {
		int p = peekChar();
		bufPos++;
		column++;
		if (p == 10) {
			line++;
			column = 1;
		}
		return p;
	}

	public String readName() throws IOException {

		int c = readChar();
		if (c < 128 && c != '_' && c != ':' && (c < 'a' || c > 'z') && (c < 'A' || c > 'Z'))
			throw new DefaultParserException("name expected!", null);

		StringBuffer buf = new StringBuffer();
		buf.append((char) c);

		while (!eof) {
			c = peekChar();

			if (c < 128 && c != '_' && c != '-' && c != ':' && c != '.' && (c < '0' || c > '9') && (c < 'a' || c > 'z')
					&& (c < 'A' || c > 'Z'))

				break;

			buf.append((char) readChar());
		}

		return buf.toString();
	}

	private int readText(StringBuffer buf, char delimiter) throws IOException {

		int type = Xml.WHITESPACE;
		int nextChar;

		while (true) {
			nextChar = peekChar();

			if (nextChar == -1 || nextChar == delimiter || (delimiter == ' ' && (nextChar == '>' || nextChar < ' ')))
				break;

			readChar();

			if (nextChar == '&') {
				String code = readTo(';', new StringBuffer()).toString();
				readChar();

				if (code.charAt(0) == '#') {
					nextChar = (code.charAt(1) == 'x' ? Integer.parseInt(code.substring(2), 16) : Integer.parseInt(code
							.substring(1)));

					if (nextChar > ' ')
						type = Xml.TEXT;

					buf.append((char) nextChar);
				} else {
					if (code.equals("lt"))
						buf.append('<');
					else if (code.equals("gt"))
						buf.append('>');
					else if (code.equals("apos"))
						buf.append('\'');
					else if (code.equals("quot"))
						buf.append('"');
					else if (code.equals("amp"))
						buf.append('&');
					else
						buf.append(resolveCharacterEntity(code));

					type = Xml.TEXT;
				}
			} else {
				if (nextChar > ' ')
					type = Xml.TEXT;
				buf.append((char) nextChar);
			}
		}

		return type;
	}

	/**
	 * Reads chars to the given buffer until the given stopChar is reached. The
	 * stopChar itself is not consumed.
	 */

	private StringBuffer readTo(char stopChar, StringBuffer buf) throws IOException {

		while (!eof && peekChar() != stopChar)
			buf.append((char) readChar());

		return buf;
	}

	private String resolveCharacterEntity(String name) throws IOException {

		throw new DefaultParserException("Undefined: &" + name + ";", null);
	}

	private void skipWhitespace() throws IOException {
		while (!eof && peekChar() <= ' ')
			readChar();
	}

	/**
	 * default is false. Setting relaxed true allows CHTML parsing
	 */

	/*
	 * public void setRelaxed (boolean relaxed) { this.relaxed = relaxed; }
	 */

	/*
	 * public int getLineNumber () { return line; }
	 */

	/*
	 * public int getColumnNumber () { return column; }
	 */

}
